<?php
namespace app\job;

use app\common\model\Prize;
use app\common\model\PrizeDraw;
use app\common\model\User;
use think\Config;
use think\Db;
use think\Exception;
use think\Queue;
use think\queue\Job;
use think\Log;

/**
 * 开奖队列
 * Class OpenPrize
 * @package app\job
 */
class OpenPrize{

    public function fire(Job $job, $data){

        // 有些消息在到达消费者时,可能已经不再需要执行了
        $isJobStillNeedToBeDone = $this->checkToBeDone($data);
        if(!$isJobStillNeedToBeDone){
            $job->delete();
            return;
        }

        //....这里执行具体的任务
        $isJobDone = $this->doJob($data);

        if ($isJobDone) {
            // 如果任务执行成功， 记得删除任务
            $job->delete();
            //print("<info>Hello Job has been done and deleted"."</info>\n");
        }else{
            if ($job->attempts() > 3) {
                //通过这个方法可以检查这个任务已经重试了几次了
                //print("<warn>Hello Job has been retried more than 3 times!"."</warn>\n");

                $job->delete();

                // 也可以重新发布这个任务
                //print("<info>Hello Job will be availabe again after 2s."."</info>\n");
                //$job->release(2); //$delay为延迟时间，表示该任务延迟2秒后再执行
            }
        }

    }

    public function failed($data){

        // ...任务达到最大重试次数后，失败了
    }

    /**
     * 有些消息在到达消费者时,可能已经不再需要执行了
     * @param array|mixed $data 发布任务时自定义的数据
     * @return boolean                 任务执行的结果
     * @throws \think\Exception
     */
    private function checkToBeDone($data = ''){
        if ($data){
            //TODO 直接在后台抽奖管理做限制 已有参与抽奖的不允许修改 字段 switch opentime
            //判断该抽奖活动是否有效，防止中途关闭开奖 或 修改开奖时间则不执行
            $map = [
                'id'        => $data['id'],
                'opentime'  => $data['opentime'],
                'switch'    => 1, //是否开启
            ];
            $prizeModel = new Prize();
            $count = $prizeModel
                ->where($map)
                ->count();

            if ($count){
                return true;
            }
        }
        $msg = '无效抽奖！';
        LOG::myLog($msg,$data,'job');
        return false;
    }

    /**
     * 根据消息中的数据进行实际的业务处理...
     * @param $data
     * @return bool
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     */
    private function doJob($data)
    {
        if ($data){
            $map = [
                'prize_id'  => $data['id'],
                'opentime'  => $data['opentime'],
                'is_opened' => 0, //未开奖的
            ];
            $prizeDrawModel = new PrizeDraw();
            $user = new User();
            //获取用户幸运等级权重
            $list = $prizeDrawModel
                ->getLevelWeight($map);

            $count = $prizeDrawModel->where($map)->count();
            if (!$count){
                $msg = '无用户参与！';
                LOG::myLog($msg,$data,'job');
                return true;
            }

            //获取中奖记录ids is_level = 1 适用幸运等级的抽奖
            if ($data['is_level'] == 1){
                $draw = $this->weight_draw($count,$data);
            }else{
                //公平抽奖
                $draw = $this->normal_draw($count,$data);
            }
            $drawList = $draw['ids'];
            if (!$drawList){
                $msg = '无用户中奖！';
                LOG::myLog($msg,$data,'job');
                return true;
            }

            Db::startTrans();
            try {
                //修改中奖的记录 is_win = 1
                $drawWhere = [
                    'id' => ['in',$drawList],
                ];
                $prizeDrawModel->save([
                    'is_win' => 1,
                ],$drawWhere);

                //重置中奖用户 幸运等级 level = 1
                $userWhere = [
                    'id' => ['in',$draw['user_ids']],
                ];
                $user->save([
                    'level' => 1,
                ],$userWhere);

                //修改全部参与抽奖的记录 is_opened = 1
                $allList = array_column($list,'id');
                $where = [
                    'id' => ['in',$allList],
                ];
                $prizeDrawModel->save([
                    'is_opened' => 1,
                ],$where);

                //noprize_auto_giveup 配置修改需要重启队列
                if (Config::get('site.noprize_auto_giveup') > 0){
                    $hour = Config::get('site.noprize_auto_giveup');
                    //自动弃奖
                    $delay = $hour * 60 *60;
                    $job = 'AutoGiveUp';
                    $queue = 'autogiveup';
                    $isPushed = Queue::later($delay,$job,$drawList,$queue);
                }

                Db::commit();

                $msg = '开奖成功！';
                LOG::myLog($msg,$data,'job');

            }catch (Exception $e){
                Db::rollback();
                $msg = $e->getMessage();
                //job日志
                LOG::myLog($msg,$data,'job');
            }

        }
        return true;
    }

    protected function normal_draw($count,$data){

        //判断中奖人数
        $num = ($count > $data['total']) ? $data['total'] : $count;

        $ids = $user_ids = $emptyLevel =  [];

        $prizeDrawModel = new PrizeDraw();
        $map = [
            'prize_id'  => $data['id'],
            'opentime'  => $data['opentime'],
            'is_opened' => 0, //未开奖的
        ];


        while (count($ids) < $num){

            $rand = ($count == 1) ? 0 : mt_rand(0,$count - 1);

            $result = $prizeDrawModel
                ->where($map)
                ->limit($rand,1)
                ->select();

            if ($result) {
                //获取id
                $id = $result[0]['id'];
                //获取user_id
                $user_id = $result[0]['user_id'];

                //判断是否存在，否则加入中奖用户
                if (!in_array($id,$ids) && !in_array($user_id,$user_ids)){
                    $ids[] = $id;
                    $user_ids[] = $user_id;
                }
            }
        }

        $data = [
            'ids' => $ids,
            'user_ids' => $user_ids,
        ];

        return $data;
    }

    /**
     * 抽奖逻辑
     * 先随机权重等级
     * 然后查询这个权重下的用户抽奖
     * 再普通随机数
     * 获取这个抽奖信息
     * @param $list
     * @return array
     */

    protected function weight_draw($count,$data){

        $sum = 0;
        $listPoint = array(0);//这个数组记录了每个切割点的值，就是记录了数轴上，5,15,30,50,100的值。
        $seed = getUserLevel();
        foreach ($seed as $k => $v){

            $sum += $v;//计算出权值的总和
            array_push($listPoint, $sum);//把分割点放到数组中
        }

        //判断中奖人数
        $num = ($count > $data['total']) ? $data['total'] : $count;

        $ids = $user_ids = $emptyLevel =  [];

        $prizeDrawModel = new PrizeDraw();
        $map = [
            'prize_id'  => $data['id'],
            'opentime'  => $data['opentime'],
            'is_opened' => 0, //未开奖的
        ];

        while (count($ids) < $num){

            //随机权重等级
            $level = $this->GetRandom($listPoint,$sum);

            if (in_array($level,$emptyLevel)) continue;

            $map['u.level'] = $level;
            $total = $prizeDrawModel
                ->alias('pd')
                ->join('fa_user u','pd.user_id=u.id','LEFT')
                ->where($map)
                ->count();

            //确认这个等级的用户，没有参与抽奖这加入临时数据，防止重复查询
            if (!$total) {
                array_push($emptyLevel,$level);
                continue;
            }

            $rand = ($total == 1) ? 0 : mt_rand(0,$total - 1);

            $result = $prizeDrawModel
                ->alias('pd')
                ->join('fa_user u','pd.user_id=u.id','LEFT')
                ->field('pd.id,pd.user_id')
                ->where($map)
                ->limit($rand,1)
                ->select();

            if ($result) {
                //获取id
                $id = $result[0]['id'];
                //获取user_id
                $user_id = $result[0]['user_id'];

                //判断是否存在，否则加入中奖用户
                if (!in_array($id,$ids) && !in_array($user_id,$user_ids)){
                    $ids[] = $id;
                    $user_ids[] = $user_id;
                }
            }
        }

        $data = [
            'ids' => $ids,
            'user_ids' => $user_ids,
        ];

        return $data;
    }

    //返回一个随机中奖
    protected function GetRandom($listPoint,$sum){

        $k = mt_rand(0, $sum-1);//取0到sum之间一个随机值

        return $this->bin_sch($listPoint,0,count($listPoint),$k);
    }

    /**
     * 二分
     * @param $array
     * @param $low
     * @param $high
     * @param $k
     * @return int
     */
    protected function bin_sch($array,  $low, $high, $k){

        if ( $low <= $high){

            $mid =  intval(($low+$high)/2 );

            if ($k >= $array[$mid] && $k < $array[$mid + 1]){
                // +1 对应 1 2 3 4 5 6
                return $mid + 1;

            }elseif ( $k < $array[$mid]){

                return  $this->bin_sch($array, $low,  $mid-1, $k);

            }else{

                return  $this->bin_sch($array, $mid+ 1, $high, $k);

            }

        }

        return -1;

    }

    //TODO 所有抽奖记录加权随机
    /*protected function draw($list,$data)
    {

        $sum = 0;
        $listPoint = array(0);//这个数组记录了每个切割点的值，就是记录了数轴上，5,15,30,50,100的值。
        foreach ($list as $k => $v){
            $value = 1;
            if ($v['value']){
                $value = $v['value'];
            }

            $sum += $value;//计算出权值的总和
            array_push($listPoint, $sum);//把分割点放到数组中
        }

        $ids = $user_ids = [];
        //判断中奖人数
        $num = (count($list) > $data['total']) ? $data['total'] : count($list);

        while (count($ids) < $num){
            $result = $this->GetRandom($listPoint,$sum);

            //获取id
            $id = $list[$result]['id'];
            //获取user_id
            $user_id = $list[$result]['user_id'];

            //判断是否存在，否则加入中奖用户
            if (!in_array($id,$ids)){
                $ids[] = $id;
                $user_ids[] = $user_id;
            }
        }

        $data = [
            'ids' => $ids,
            'user_ids' => $ids,
        ];

        return $data;
    }*/

    //TODO 一个抽奖记录生成队员种子数 随机这个范围
    /*protected function draw($list,$data)
{

    //获取用户等级 获取种子数,所有prize_draw 生成 种子数 序列
    //打乱数组，rand ，取下表 或者 array_rand 取出
    //如果prize_draw_id 不在中奖名单中 则加入到名单数组
    //奖品总数 就是数组的长度

    //用户幸运等级 对应种子个数
    $seed = [
        1 => 1,
        2 => 3,
        3 => 9,
        4 => 18,
        5 => 36,
        6 => 72,
    ];

    //生成抽奖种子数组
    $array = [];
    foreach ($list as $k => $v){
        $v['user']['level'];
        if (isset($seed[$v['user']['level']])){
            for($i=0;$i < $seed[$v['user']['level']];$i++){
                $array[] = $v['id'];
            }
        }
    }

    $ids = [];
    //判断中奖人数
    $num = (count($list) > $data['total']) ? $data['total'] : count($list);

    //随机抽取 $num 个抽奖
    while (count($ids) < $num){
        $result = array_rand($array,1);
        //判断是否存在，否则加入中奖用户
        if (!in_array($array[$result],$ids)){
            $ids[] = $array[$result];
        }
    }

    return $ids;
}*/

}